#!/bin/bash

# helpers
echov() {
  [[ "$verbose" -eq 1 ]] && echo "VERBOSE: $*"
}

pidfile-path() {
  echo "$1/.autotags.pid"
}

pid-running() {
  kill -0 "$1" > /dev/null 2>&1
}

daemon-status() {
  local path=$1
  local pidfile; pidfile=$(pidfile-path "$1")

  echov "checking if daemon is running in $path"

  if [ -f "$pidfile" ]; then
    local pid; pid="$(cat "$pidfile")"
    echov "found pidfile in $pidfile (pid is $pid)"

    if pid-running "$pid"; then
      return 0
    else
      return 2
    fi
  else
    return 1
  fi
}

abspath() {
  pushd "$1" > /dev/null 2>&1 || return 1
  pwd
  popd > /dev/null 2>&1 || return 1
}

display-running() {
  echo "looks like autotags is running in $1"
}

display-dangling-pidfile() {
  local pidfile; pidfile=$(pidfile-path "$1")

  echo "looks like autotags in not running in $1"
  echo "warning: the lock file $pidfile exists"
  echo 'please delete the file if autotags is really not running'
}

display-not-running() {
  echo "looks like autotags is not running in $1"
}

display-help() {
  cat <<'HELP'
usage: autotags [options] <command> <path>
  command: a command to run (see below)
  path:    directory to operate on (defaut: .)

Options:
  -v:  enable verbose logging
  -h:  display this help message

Commands:
  help:    display this help message
  status:  display whether or the given path is being watched or not
  watch:   monitor the given directory for changes and generate ctags
  stop:    stop monitoring the given directory
HELP
}

# options
parse-options() {
  export verbose=0
  export path=.
  OPTIND=1
  while getopts 'hv' opt; do
    case "$opt" in
      v) export verbose=1;;
      h|?) display-help; exit 0;;
    esac
  done
  shift $((OPTIND - 1))

  if [[ "$#" -lt "1" ]]; then
    echo 'too few arguments'
    display-help
    exit 1
  fi

  export cmd=$1
  [[ "$#" -gt "1" ]] && export path=$2
}

# commands
status() {
  local path=$1

  daemon-status "$path"
  case $? in
    0) display-running "$path"; exit 0;;
    1) display-not-running "$path"; exit 1;;
    2) display-dangling-pidfile "$path"; exit 2;;
  esac
}

stop() {
  local path=$1

  daemon-status "$path"
  case $? in
    0)
      local pid; pid=$(cat "$(pidfile-path "$path")")
      echov "killing pid $pid"
      kill -15 "$pid"
      exit 0
      ;;
    1) display-not-running "$path"; exit 1;;
    2) display-dangling-pidfile "$path"; exit 2;;
  esac
}

watch() {
  local path=$1
  daemon-status "$path"
  case $? in
    0) display-running; exit 1;;
    1)
      echov "starting watch daemon in $path"
      (run-watch "$path" &) &
      exit 0
      ;;
    2) display-dangling-pidfile; exit 2;;
  esac
}

gen-ctags() {
  local path=$1
  echov "generating $path/.tags from files in $path"
  ctags -R -o "$path/.tags" "$path"
}

gen-ctags-on-input() {
  while true; do
    read -r input
    echov "Watch event fired: $input"
    gen-ctags "$1"
  done
}

run-watch() {
  local pid=$BASHPID
  echov 'trapping SIGTERM'
  trap 'pgrep -P $pid | xargs kill -15' SIGTERM

  local path; path=$(abspath "$1")
  local pidfile; pidfile=$(pidfile-path "$path")
  echov "writing pid ($pid) to pidfile ($pidfile)"
  echo $pid > "$pidfile"

  echov 'changing dir to root'
  cd / || return 1

  gen-ctags "$path"

  echov 'starting watch'
  inotifywait -mrqc -e modify,move,create,delete --exclude .tags "$path" \
    | gen-ctags-on-input "$path" &

  wait
  echov 'done watching'

  echov "removing pidfile ($pidfile)"
  rm -rf "$pidfile"
}

dispatch-command() {
  local cmd=$1
  local path=$2

  case "$cmd" in
    status) status "$path" ;;
    watch) watch "$path" ;;
    stop) stop "$path" ;;
    help) display-help ;;
    *)
      echo "unknown command: $cmd"
      display-help
      exit 1
      ;;
  esac
}

parse-options "$@"

echov "verbose=$verbose cmd=$cmd path=$path"
dispatch-command "$cmd" "$path"
